﻿#include "pch.h"
#include "MainTaskThread.h"
#include "AsyncLibImpl.h"

#define WM_RUN_TASK   WM_USER + 1000
#define WM_ADD_TIMER  WM_USER + 1001
#define WM_SET_TIMER  WM_USER + 1002
#define WM_DEL_TIMER  WM_USER + 1003

extern HMODULE g_hModule;

namespace AsyncLib
{

CMainTaskThread *CMainTaskThread::m_pThis;

CMainTaskThread::CMainTaskThread() : m_hWnd(NULL), m_pfnOldWndProc(NULL), m_bHandling(false), m_nLastTimerId(0)
{
	assert(!m_pThis);
	m_pThis = this;
}

CMainTaskThread::~CMainTaskThread()
{
	assert(m_hWnd == NULL);
	assert(m_mapTimers.empty());
	m_pThis = nullptr;
}

void CMainTaskThread::CreateWnd()
{
	m_hWnd = CreateWindowExW(0, L"Message", NULL, WS_POPUP, 0, 0, 0, 0, HWND_MESSAGE, NULL, g_hModule, NULL);
	assert(m_hWnd);

	WNDPROC pfnWndProc = [](HWND, UINT nMsg, WPARAM wParam, LPARAM lParam)
	{
		return m_pThis->WndProc(nMsg, wParam, lParam);
	};

	m_pfnOldWndProc = (WNDPROC)SetWindowLongPtrW(m_hWnd, GWLP_WNDPROC, (LONG_PTR)pfnWndProc);
	assert(m_pfnOldWndProc);
}

void CMainTaskThread::Stop()
{
}

void CMainTaskThread::Exit()
{
	if (m_hWnd == NULL)
		return;

	// Destroy the window synchronously
	SendMessage(m_hWnd, WM_CLOSE, 0, 0);
	m_hWnd = NULL;
	m_mapTimers.clear();

	// Destruct pending tasks sequentially
	while (m_deqTasks.size())
	{
		m_deqTasks.front()();
		m_deqTasks.pop_front();
	}
}

void CMainTaskThread::PostTask(std::function<void()>&& fnTask)
{
	assert(m_hWnd);

	if (m_hWnd == NULL)
		return;

	std::unique_lock lock(m_mutex);
	m_deqTasks.push_back(std::move(fnTask));

	if (!m_bHandling)
	{
		m_bHandling = true;
		PostMessage(m_hWnd, WM_RUN_TASK, 0, 0);
	}
}

std::shared_ptr<ITimerTask> CMainTaskThread::CreateTimer()
{
	assert(m_hWnd);

	if (m_hWnd == NULL)
		return nullptr;

	UINT nTimerId = InterlockedIncrement(&m_nLastTimerId);
	auto pTimerTask = std::make_shared<CUiTimerTask>(m_hWnd, nTimerId);
	SendMessage(m_hWnd, WM_ADD_TIMER, nTimerId, (LPARAM)&pTimerTask);
	assert(pTimerTask.use_count() > 1);
	return pTimerTask;
}

LRESULT CMainTaskThread::WndProc(UINT nMsg, WPARAM wParam, LPARAM lParam)
{
	switch (nMsg)
	{
	case WM_RUN_TASK:
		RunTask();
		break;

	case WM_ADD_TIMER:
		m_mapTimers.emplace(wParam, *(std::shared_ptr<CUiTimerTask> *)lParam);
		break;

	case WM_SET_TIMER:
		if (lParam)
			SetTimer(m_hWnd, wParam, (UINT)lParam, NULL);
		else
			KillTimer(m_hWnd, wParam);
		break;

	case WM_DEL_TIMER:
		m_mapTimers.erase(wParam);
		KillTimer(m_hWnd, wParam);
		break;

	case WM_TIMER:
		OnTimer(wParam);
		break;

	default:
		return CallWindowProc(m_pfnOldWndProc, m_hWnd, nMsg, wParam, lParam);
	}

	return 0;
}

void CMainTaskThread::RunTask()
{
	std::unique_lock lock(m_mutex);

	while (g_asyncLib.IsRunning() && m_deqTasks.size())
	{
		auto fnTask = std::move(m_deqTasks.front());
		m_deqTasks.pop_front();
		lock.unlock();

		fnTask();
		fnTask = nullptr;

		lock.lock();
	}

	m_bHandling = false;
}

void CMainTaskThread::OnTimer(UINT_PTR nTimerId)
{
	if (auto it = m_mapTimers.find(nTimerId); it != m_mapTimers.end())
		it->second->DoCallback();
}

void CUiTimerTask::Start(UINT nElapse)
{
	if (IsWindow(m_hManager))
		SendMessage(m_hManager, WM_SET_TIMER, m_nTimerId, nElapse);
}

void CUiTimerTask::Stop()
{
	if (IsWindow(m_hManager))
		SendMessage(m_hManager, WM_SET_TIMER, m_nTimerId, 0);
}

void CUiTimerTask::Close()
{
	if (IsWindow(m_hManager))
		SendMessage(m_hManager, WM_DEL_TIMER, m_nTimerId, 0);
}

} // namespace AsyncLib
